package com.zwf.checkstyledemo1.checks;

import com.puppycrawl.tools.checkstyle.api.*;
import java.util.*;

public class NullDereferenceCheck2 extends AbstractCheck {
    private static final int INDEX_OP = TokenTypes.INDEX_OP;
    private final Set<String> nullVariables = new HashSet<>();
    private final Map<DetailAST, String> delayedDereferences = new HashMap<>();

    @Override
    public int[] getDefaultTokens() {
        return getRequiredTokens();
    }

    @Override
    public int[] getAcceptableTokens() {
        return getRequiredTokens();
    }

    @Override
    public int[] getRequiredTokens() {
        return new int[] {
                TokenTypes.DOT,
                INDEX_OP,
                TokenTypes.METHOD_CALL,
                TokenTypes.ASSIGN,
                TokenTypes.VARIABLE_DEF,
                TokenTypes.EXPR
        };
    }

    @Override
    public void beginTree(DetailAST rootAST) {
        nullVariables.clear();
        delayedDereferences.clear();
    }

    @Override
    public void finishTree(DetailAST rootAST) {
        // 处理所有延迟的解引用检查
        for (Map.Entry<DetailAST, String> entry : delayedDereferences.entrySet()) {
            DetailAST ast = entry.getKey();
            String varName = entry.getValue();
            if (nullVariables.contains(varName)) {
                reportNullDereference(ast, varName);
            }
        }
    }

    @Override
    public void visitToken(DetailAST ast) {
        switch (ast.getType()) {
            case TokenTypes.ASSIGN:
                processAssignment(ast);
                break;
            case TokenTypes.VARIABLE_DEF:
                processVariableDef(ast);
                break;
            case TokenTypes.DOT:
                checkDotOperator(ast);
                break;
            case INDEX_OP:
                checkArrayAccess(ast);
                break;
            case TokenTypes.METHOD_CALL:
                checkMethodCall(ast);
                break;
            case TokenTypes.EXPR:
                checkExpression(ast);
                break;
        }
    }

    private void processAssignment(DetailAST assignAst) {
        DetailAST lhs = getLeftOperand(assignAst);
        DetailAST rhs = getRightOperand(assignAst);

        if (lhs != null && rhs != null && isNullLiteral(rhs)) {
            if (lhs.getType() == TokenTypes.IDENT) {
                nullVariables.add(lhs.getText());
            }
        }
    }

    private void processVariableDef(DetailAST varDefAst) {
        DetailAST assign = varDefAst.findFirstToken(TokenTypes.ASSIGN);
        if (assign != null) {
            DetailAST lhs = getLeftOperand(assign);
            DetailAST rhs = getRightOperand(assign);

            if (lhs != null && rhs != null && isNullLiteral(rhs)) {
                DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
                if (ident != null) {
                    nullVariables.add(ident.getText());
                }
            }
        }
        // 处理直接赋值为null的变量定义（没有ASSIGN节点）
        else {
            DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
            if (ident != null) {
                // 检查变量类型是否为对象类型（非基本类型）
                if (isObjectType(varDefAst)) {
                    nullVariables.add(ident.getText());
                }
            }
        }
    }

    private boolean isObjectType(DetailAST varDefAst) {
        DetailAST typeAst = varDefAst.findFirstToken(TokenTypes.TYPE);
        if (typeAst != null) {
            String typeName = typeAst.getFirstChild().getText();
            // 排除基本数据类型
            return !typeName.matches("int|long|float|double|boolean|char|byte|short");
        }
        return false;
    }

    private void checkExpression(DetailAST exprAst) {
        DetailAST child = exprAst.getFirstChild();
        if (child != null && child.getType() == TokenTypes.METHOD_CALL) {
            checkMethodCall(child);
        }
    }

    private void checkDotOperator(DetailAST dotAst) {
        DetailAST expr = getDotExpression(dotAst);
        if (expr == null) return;

        // 立即检查字面量null
        if (isLiteralNull(expr)) {
            reportNullDereference(dotAst, "字面量null");
            return;
        }

        // 延迟检查变量解引用
        if (expr.getType() == TokenTypes.IDENT) {
            String varName = expr.getText();
            if (nullVariables.contains(varName)) {
                reportNullDereference(dotAst, varName);
            } else {
                delayedDereferences.put(dotAst, varName);
            }
        }
    }

    private void checkArrayAccess(DetailAST indexOpAst) {
        DetailAST expr = getArrayExpression(indexOpAst);
        if (expr == null) return;

        // 立即检查字面量null
        if (isLiteralNull(expr)) {
            reportNullDereference(indexOpAst, "字面量null");
            return;
        }

        // 延迟检查变量解引用
        if (expr.getType() == TokenTypes.IDENT) {
            String varName = expr.getText();
            if (nullVariables.contains(varName)) {
                reportNullDereference(indexOpAst, varName);
            } else {
                delayedDereferences.put(indexOpAst, varName);
            }
        }
    }

    private void checkMethodCall(DetailAST methodCallAst) {
        DetailAST dotAst = methodCallAst.getFirstChild();
        if (dotAst != null && dotAst.getType() == TokenTypes.DOT) {
            checkDotOperator(dotAst);
        }
    }

    private boolean isLiteralNull(DetailAST expr) {
        return expr != null && expr.getType() == TokenTypes.LITERAL_NULL;
    }

    private boolean isNullLiteral(DetailAST ast) {
        return ast != null && ast.getType() == TokenTypes.LITERAL_NULL;
    }

    private DetailAST getLeftOperand(DetailAST assignAst) {
        return assignAst.getFirstChild();
    }

    private DetailAST getRightOperand(DetailAST assignAst) {
        DetailAST lhs = assignAst.getFirstChild();
        return lhs != null ? lhs.getNextSibling() : null;
    }

    private DetailAST getDotExpression(DetailAST dotAst) {
        return dotAst.getFirstChild();
    }

    private DetailAST getArrayExpression(DetailAST indexOpAst) {
        return indexOpAst.getFirstChild();
    }

    private void reportNullDereference(DetailAST ast, String reason) {
        String message = "空指针解引用风险: ";
        if (reason.startsWith("字面量")) {
            message += "在" + reason + "上";
        } else {
            message += "在可能为null的变量 '" + reason + "' 上";
        }

        switch (ast.getType()) {
            case TokenTypes.DOT:
                message += "调用方法或访问属性";
                break;
            case INDEX_OP:
                message += "进行数组访问";
                break;
        }

        log(ast, message);
    }
}